/*
 * Borovicska Szglab4 2011
 * @package circuit1
 */
package circuit1;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.yaml.snakeyaml.Yaml;

/**
 * YamlAdapter
 * 
 * @author Gergely Hernesz <herneszg@gmail.com>
 */
public class YamlAdapter implements ICircuitAdapter {

    private Yaml yaml;
    private Map yamlData;
    private Circuit circuit = new Circuit();
    
    private ArrayList<String> from = new ArrayList<String>();
    private ArrayList<String> to = new ArrayList<String>();
    
    private boolean sorted;
    
    public YamlAdapter(String file) {
        sorted = false;
        yaml = new Yaml();
        yamlData = loadFile(file);
        ////System.out.println(file + " loaded");
        parse(yamlData);
    }

    private void parse(Map data) {
        Set c = data.entrySet();
        Iterator i = c.iterator();
        while (i.hasNext()) {
            //System.out.println("next");
            Map.Entry e = (Map.Entry) i.next();
            if (e.getKey().equals("circuit")) {
                parseCircuit((Map) e.getValue());
            } else if (e.getKey().toString().matches("composite[0-9]{0,5}")){
                //System.out.println("matches");
                parseCompositeType((Map) e.getValue());
            }
        }
    }
    
    public Circuit getCircuit() {
        if(!sorted) {
            sortWires();
        }
        return this.circuit;
    }
    
    private void parseCompositeType(Map map) {
        String name = map.get("name").toString();
        ArrayList pins = (ArrayList)map.get("pins");
        ArrayList elements = (ArrayList)map.get("elements");
        this.circuit.addCompositeType(new CompositeType(name, pins, elements));
    }
    
    /**
     * Aramkor parsolasa
     * @param Map circuit 
     */
    private void parseCircuit(Map circuitData) {
        try {
            parseElements(this.circuit, (ArrayList) circuitData.get("elements"));
        } catch (Exception e) {
            ////System.out.println(e);
        }
    }
    
    /**
     *  Kompozit elem parsolasa
     * @param Map compositeData 
     */
    private void parseComposite(Composite comp, CompositeType ct) {
        try {
            comp.setName(ct.getName());
            parsePins(comp, ct.getPins());
            parseElements(comp, ct.getElements());
        } catch (Exception e) {
            ////System.out.println(e);
            ////System.out.println(e.getStackTrace());
        }
        ////System.out.println("parseComposite vege");
    }
    
    /**
     * Pinek letrehozasa 
     */
    private void parsePins(Composite c, ArrayList elementsList) throws Exception {
        for (Iterator i = elementsList.iterator(); i.hasNext();) {
            Map pinMap = (Map) i.next();
            String name = (String) pinMap.get("name");
            String input = (String) pinMap.get("input");
            String output = (String) pinMap.get("output");
            if (output!=null) {
                if(c.getWireById(output) == null){
                    c.addWire(createWire(output));
                    ////System.out.println("Wire created: " + output);
            }
                Pin pin = new Pin();
                pin.setId(name);
                //pin.configPins();
                pin.setInput(c.getWireById(output));
                //pin.setOutput(c.getWireById(name));
                c.addPin(pin);
                c.addElement(pin);
                ////System.out.println("Wire created: " + output);
                ////System.out.println(c.getElements());
            } else if (input!=null) {
                if(c.getWireById(input) == null){
                    c.addWire(createWire(input));
                    ////System.out.println("Wire created: " + input);
                }
                Pin pin = new Pin();
                pin.setId(name);
                //pin.configPins();
                pin.setOutput(c.getWireById(input));
                //pin.setOutput(c.getWireById(name));
                c.addPin(pin);
                c.addElement(pin);
            }
            
        }
    }

    /**
     * Elemek letrehozasa
     * @param ArrayList elements
     * @throws Exception 
     */
    private void parseElements(Circuit c, ArrayList elementsList) throws Exception {
        for (Iterator i = elementsList.iterator(); i.hasNext();) {
            Map element = (Map) i.next();
            String type = (String) element.get("type");
            //System.out.println("tipus: " + type);
            for(int j=0; j<circuit.getCompositeTypes().size(); j++){
                //System.out.println(circuit.getCompositeTypes().get(j).getName());
                }
            IElement e;
            if (type.equals("GEN")) {
                e = new Generator();
                configWires(c, element, (Generator) e);
                ((Generator) e).setSignal(element.get("value").toString());
                c.getGenerators().add((Generator)e);
                ////System.out.println("GEN letrehozva");
            } else if (type.equals("SW")) {
                e = new Switch();
                configWires(c, element, (Switch) e);
                ((Switch) e).setSignal(element.get("value").toString().equals("1"));
                ////System.out.println("SW letrehozva");
            } else if (type.equals("LED")) {
                e = new Led();
                configWires(c, element, (Led) e);
                ////System.out.println("LED letrehozva");
            } else if (type.equals("OSC")) {
                e = new Oscilloscope();
                configWires(c, element, (Oscilloscope) e);
                ////System.out.println("OSC letrehozva");
            } else if (type.equals("OR")) {
                ////System.out.println(element.get("inputs"));
                e = new Or();
                configWires(c, element, (Or) e);
                ////System.out.println("OR letrehozva");
            } else if (type.equals("AND")) {
                e = new And();
                configWires(c, element, (And) e);
                ////System.out.println("AND letrehozva");
            } else if (type.equals("INV")) {
                e = new Inverter();
                configWires(c, element, (Inverter) e);
                ////System.out.println("INV letrehozva");
            } else if(isCompositeType(type) != null) {  
                //for(Wire w : circuit.getWires()){
                    ////System.out.println("wwwwwww: " + w.getId());
                //}
                Composite comp = new Composite();
                parseComposite(comp, c.getCompositeTypeByName(type));
                comp.setName(element.get("name").toString());
                setPins(comp, c, (ArrayList)element.get("pins"));
                this.circuit.addCompositeElement(comp);
                /*//System.out.println("IsComposite");
                c.addCompositeElement((Composite)c.getCompositeTypeByName(type).clone());
                //System.out.println("listahoz adas utan");
                c.getCompositeElements().get(c.getCompositeElements().size()-1).setName(element.get("name").toString());
                setPins(c.getCompositeElements().get(c.getCompositeElements().size()-1), c, (ArrayList)element.get("pins"));
                //System.out.println("set pins utan");
                //System.out.println("PIN NAME: " + c.getCompositeTypeByName(type).getPinByName("PIN04").getOutput().getId());
                */
                ////System.out.println("Kompozit elem letrehozva");
                continue;
            } else {
                throw new Exception("Unknown element type!");
            }
            if(isCompositeType(type) == null){
                e.setId(element.get("name").toString());
            }
            c.addElement(e);
        }
    }
    
    private void setPins(Composite comp, Circuit circ, ArrayList list){
        for (Iterator i = list.iterator(); i.hasNext();) {
            Map pinMap = (Map) i.next();
            String name = (String) pinMap.get("name");
            String input = (String) pinMap.get("input");
            String output = (String) pinMap.get("output");
            //IElement e;
            if (input != null) {
                if(circ.getWireById(input) == null){
                    //System.out.println("ADD WIRE INPUT");
                    circ.addWire(createWire(input));
                }
                ////System.out.println(circ.getWireById(input).getId());
                comp.getPinByName(name).setInput(circ.getWireById(input));
                //System.out.println("SET PINS?: " + comp.getName());
            } else if (output != null) {
                //System.out.println("ADD WIRE OUTPUT");
                if(circ.getWireById(input) == null){
                    circ.addWire(createWire(output));
                }
                comp.getPinByName(name).setOutput(circ.getWireById(output));
            }
        }
                
    }
    
    private CompositeType isCompositeType(String name){
        for(int i=0; i<circuit.getCompositeTypes().size(); i++){
            if(circuit.getCompositeTypes().get(i).getName().equals(name))
                return circuit.getCompositeTypes().get(i);
        }
        return null;
    }
    
    private Wire createWire(String w) {
        Wire wire = new Wire();
        wire.setId(w);
        return wire;
    }
    
    private void configWires(Circuit c, Map map, Gate element){
        if(map.get("input") != null){
            String ID = map.get("input").toString();
            if(c.getWireById(ID) == null){
                c.addWire(createWire(ID));
                ////System.out.println("Wire created: " + ID);
            }
            element.addInput(c.getWireById(ID));
        }
        if(map.get("inputs") != null){
            ArrayList<String> IDs = (ArrayList<String>)map.get("inputs");
            for(int i = 0; i<IDs.size(); i++){
                if(c.getWireById(IDs.get(i)) == null){
                    c.addWire(createWire(IDs.get(i)));
                    ////System.out.println("Wire created: " + IDs.get(i));
                }
                element.addInput(c.getWireById(IDs.get(i)));
            }
        }
        if(map.get("output") != null){
            String ID = map.get("output").toString();
            if(c.getWireById(ID) == null){
                c.addWire(createWire(ID));
                ////System.out.println("Wire created: " + ID);
            }
            element.setOutput(c.getWireById(ID));
        }
            
    }
    
    private void configWires(Circuit c, Map map, Display element){
        if(map.get("input") != null){
            String ID = map.get("input").toString();
            if(c.getWireById(ID) == null){
                c.addWire(createWire(ID));
                ////System.out.println("Wire created: " + ID);
            }
            element.setInput(c.getWireById(ID));
        }            
    }
        
    private void configWires(Circuit c, Map map, SignalSource element){
        if(map.get("output") != null){
            String ID = map.get("output").toString();
            if(c.getWireById(ID) == null){
                c.addWire((createWire(ID)));
                ////System.out.println("Wire created: " + ID);
            }
            element.setOutput(c.getWireById(ID));
        }    
    }

    /**
     * Kimeneti wire alapjan visszater az elem azonositojaval
     * @param String o
     * @return 
     */
    private String getElementIdByOutputWire(String o) {
        return "e" + o;
    }

    /**
     * Betolti a yaml filet
     * @param file
     * @return 
     */
    private Map loadFile(String file) {
        Map data = null;
        try {
            data = (Map) yaml.load(new FileInputStream(new File(file)));
        } catch (FileNotFoundException e) {
            Logger.Log(file + " input file not found!");
            System.exit(0);
        }
        return data;
    }

    /**
     * Visszaadja a bemeneti elem alapjan a kimenetet hasznalo elemeket
     * @param e
     * @return 
     */
    /*public ArrayList<IElement> getConnectedElements(Circuit c, IElement e) {
        String wireId;
        wireId = e.getId().substring(1);
        return getElementsByInputWireId(c, wireId);
    }*/
    
    /**
     * Wire azonosito alapjan visszaadja azokat az elemeket, amiknek
     * bemenetukon szerepel
     * @param id
     * @return 
     */

    /*private ArrayList<IElement> getElementsByInputWireId(Circuit c, String  id) {
        ArrayList<String> in = new ArrayList<String>();
        ArrayList<String> out = new ArrayList<String>();
        ArrayList elementsList = (ArrayList)((Map)yamlData.get("circuit")).get("elements");
        
        
        
        
        ArrayList<IElement> elementList = new ArrayList<IElement>();
        ArrayList elementsList = (ArrayList)((Map)yamlData.get("circuit")).get("elements");
        for (Iterator i = elementsList.iterator(); i.hasNext();) {
            Map element = (Map) i.next();
            if (element.get("input") != null && element.get("input").equals(id)) {
                elementList.add(c.getElementById(getElementIdByOutputWire((String)element.get("output"))));
            } else if (element.get("inputs") != null) {
                for (Iterator ii = ((ArrayList)element.get("inputs")).iterator(); ii.hasNext();) {
                    if (ii.next().equals(id)) {
                        elementList.add(c.getElementById(getElementIdByOutputWire((String)element.get("output"))));
                    }
                }
            }
        }
        return elementList;
    }*/
    
    /**
     * A metodus letrehozza az elemek osszekotesenek ellistajat
     */
    public void sortWires() {
        ArrayList<String> inputId = new ArrayList<String>();
        ArrayList<String> inputWire = new ArrayList<String>();
        
        ArrayList<String> outputId = new ArrayList<String>();
        ArrayList<String> outputWire = new ArrayList<String>();
        
        ArrayList<String> pinId = new ArrayList<String>();
        ArrayList<String> pinWire = new ArrayList<String>();
        ArrayList<String> compID = new ArrayList<String>();
    
        ArrayList elementsList = (ArrayList)((Map)yamlData.get("circuit")).get("elements");
        
        for (Iterator i = elementsList.iterator(); i.hasNext();) {
            Map element = (Map) i.next();
            ////System.out.println(element.toString());
            if(element.get("inputs") != null){
                ArrayList<String> IDs = (ArrayList<String>)element.get("inputs");
                for(String str : IDs) {
                    inputWire.add(str);
                    inputId.add(element.get("name").toString());
                }
            }
            if(element.get("input") != null){
                    ////System.out.println("input");
                    inputWire.add(element.get("input").toString());
                    inputId.add(element.get("name").toString());
            }
            if(element.get("output") != null){
                    ////System.out.println("input");
                    outputWire.add(element.get("output").toString());
                    outputId.add(element.get("name").toString());
            }
            
            if(element.get("pins") != null){
                ArrayList al = (ArrayList)element.get("pins");
                for (Iterator it = al.iterator(); it.hasNext();) {
                    Map pinMap = (Map) it.next();
                    pinId.add(element.get("name").toString());
                    if(pinMap.get("input") != null)
                        pinWire.add(pinMap.get("input").toString());
                    else
                        pinWire.add(pinMap.get("output").toString());
                }
                
            }
        }
        for(int i=0; i<inputWire.size(); i++){
            for(int j=0; j<outputWire.size(); j++){
                if(inputWire.get(i).equals(outputWire.get(j))){
                    from.add(outputId.get(j));
                    to.add(inputId.get(i));
                }
                    
            }
        }
        for(int i=0; i<inputWire.size(); i++){
            for(int j=0; j<pinWire.size(); j++){
                if(inputWire.get(i).equals(pinWire.get(j))){
                    from.add(pinId.get(j));
                    to.add(inputId.get(i));
                }
                    
            }
        }
        for(int i=0; i<outputWire.size(); i++){
            for(int j=0; j<pinWire.size(); j++){
                if(outputWire.get(i).equals(pinWire.get(j))){
                    from.add(outputId.get(i));
                    to.add(pinId.get(j));
                }
                    
            }
        }
        for(int i=0; i<pinWire.size(); i++){
            for(int j=0; j<pinWire.size(); j++){
                if(pinWire.get(i).equals(pinWire.get(j)) && !pinId.get(i).substring(0, 2).equals(pinId.get(j).substring(0, 2))){
                    //System.out.println(pinId.get(i));
                    from.add(pinId.get(i));
                    to.add(pinId.get(j));
                }
                    
            }
        }

        circuit.setWires(from, to);
        sorted = true;
        
        /*//System.out.println("in" + inputId);
        //System.out.println("in" + inputWire);
        //System.out.println("out" + outputId);
        //System.out.println("out" + outputWire);
        //System.out.println("pin" + pinId);
        //System.out.println("pin" + pinWire);
        //System.out.println("from" + from);
        //System.out.println("to" + to);*/
    }

    /**
     * 
     * @return ArrayList
     */
    private ArrayList<Wire> getDefinedWires() {
        ArrayList<Wire> wires = new ArrayList<Wire>();
        return wires;
    }
}
